package apollo

import (
	"context"
	"encoding/json"
	"fmt"
	"log"
	"reflect"
	"strconv"
	"strings"
	"time"

	"algo-sdk-go/common/logs"

	"github.com/apolloconfig/agollo/v4"
	"github.com/apolloconfig/agollo/v4/env/config"
	"github.com/apolloconfig/agollo/v4/storage"
	"github.com/ghodss/yaml"
	"github.com/zeromicro/go-zero/core/stringx"
	"github.com/zeromicro/go-zero/core/threading"
)

const (
	ContextCancel = "context canceled"
)

type AConfig struct {
	AppID            string
	BackupConfigPath string `json:",optional"`
	Cluster          string
	NamespaceName    string `json:",optional"`
	IP               string
	IsBackupConfig   bool
	Secret           string
	PublicNameSpace  string   `json:",optional"`
	Key              string   `json:",optional"`
	Namespaces       []string `json:",optional"`
}

const comma = ","

var ls = logs.Scope("apollo-config")
var ChangedEvents []ChangedEvent

type ChangedEvent struct {
	NameSpace    string
	EventHandler func(Changes map[string]*storage.ConfigChange)
}

func ChangedEventRegister(changedEvent ChangedEvent) {
	if len(ChangedEvents) == 0 {
		ChangedEvents = make([]ChangedEvent, 0)
	}
	ChangedEvents = append(ChangedEvents, changedEvent)
}

// LoadConfigCache 配置读取错误全部panic
func LoadConfigCache(aConfig *AConfig, space string) (content string) {
	ls = ls.F("AppID", aConfig.AppID).
		F("Cluster", aConfig.Cluster).
		F("IP", aConfig.IP).
		F("NamespaceName", space)
	c := &config.AppConfig{
		AppID:          aConfig.AppID,
		Cluster:        aConfig.Cluster,
		IP:             aConfig.IP,
		NamespaceName:  space,
		IsBackupConfig: aConfig.IsBackupConfig,
		Secret:         aConfig.Secret,
	}
	client, err := agollo.StartWithConfig(func() (*config.AppConfig, error) {
		return c, nil
	})
	if err != nil {
		ls.WM("LoadConfigCache").Fatal(err)
	}
	fmt.Println("初始化Apollo配置成功")
	//Use your apollo key to test
	nameSpace := client.GetConfig(space)
	content = nameSpace.GetStringValue("content", "")
	return
}

func (c *AConfig) Unmarshal(tag string, m interface{}) (err error) {
	ls = ls.F("AppID", c.AppID).
		F("Cluster", c.Cluster).
		F("IP", c.IP).
		F("NamespaceName", c.NamespaceName)
	ac := c.generateAppConfig()
	var client agollo.Client
	if client, err = agollo.StartWithConfig(func() (*config.AppConfig, error) {
		return ac, nil
	}); err != nil {
		ls.WM("LoadConfig").Fatal(err)
	}

	//client.AddChangeListener(&myConfigChangeListener{})
	return c.initConfig(c.initNameSpaces(client), tag, m)
}

func (c *AConfig) generateAppConfig() *config.AppConfig {
	return &config.AppConfig{
		AppID:            c.AppID,
		Cluster:          c.Cluster,
		IP:               c.IP,
		NamespaceName:    c.NamespaceName,
		IsBackupConfig:   c.IsBackupConfig,
		Secret:           c.Secret,
		BackupConfigPath: c.BackupConfigPath,
	}
}

func (c *AConfig) initNameSpaces(ct agollo.Client) (spaces []*storage.Config) {
	names := strings.Split(c.NamespaceName, comma)
	spaces = make([]*storage.Config, 0, len(names))
	for _, name := range names {
		if space := ct.GetConfig(name); space != nil {
			spaces = append(spaces, space)
		} else {
			ls.WM("initNameSpaces").Fatalf("获取 Apollo Namespace: %s 失败", name)
		}
	}
	return
}

func (c *AConfig) initConfig(spaces []*storage.Config, tag string, m interface{}) (err error) {
	fType := reflect.TypeOf(m).Elem()
	Value := reflect.ValueOf(m).Elem()
	for i := 0; i < fType.NumField(); i++ {
		tField := fType.Field(i)
		tagValue, ok := tField.Tag.Lookup(tag)
		keys := strings.Split(tagValue, ",")
		if !ok || keys[0] == "" {
			keys[0] = tField.Name
		}
		value := ""
		for _, space := range spaces {
			value, err = getValue(keys[0], space)
			if err != nil {
				panic(fmt.Errorf("未查询到key:%s error:%s", keys[0], err.Error()))
			}

			if value != "" {
				break
			}
		}
		required := !stringx.Contains(keys, "optional") && !stringx.Contains(keys, "omitempty")
		if required && value == "" {
			return fmt.Errorf("Get Apollo config value failed! Key: %s ", keys[0])
		}
		vField := Value.FieldByName(tField.Name)
		if err = c.parse(keys[0], value, vField); err != nil {
			return fmt.Errorf("key:[%s] ,value parse err:%v", keys[0], err)
		}
	}
	return
}

func (c *AConfig) parse(key, value string, vField reflect.Value) (err error) {
	var rValue reflect.Value
	switch vField.Kind() {
	case reflect.Bool:
		if b, e := strconv.ParseBool(value); e != nil {
			ls.M("parse").F(key, value).Error(e)
		} else {
			rValue = reflect.ValueOf(b)
		}
	case reflect.Float32, reflect.Float64:
		if f, e := strconv.ParseFloat(value, 64); e != nil {
			ls.M("parse").F(key, value).Error(e)
		} else {
			rValue = reflect.ValueOf(f).Convert(vField.Type())
		}
	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
		if i, e := strconv.Atoi(value); e != nil {
			ls.M("parse").F(key, value).Error(e)
		} else {
			rValue = reflect.ValueOf(i).Convert(vField.Type())
		}
	case reflect.Map, reflect.Slice, reflect.Struct:
		entity := reflect.New(vField.Type()).Interface()
		if err = c.unmarshal(value, entity); err != nil {
			return
		}
		rValue = reflect.ValueOf(entity).Elem()
	default:
		rValue = reflect.ValueOf(value)
	}
	vField.Set(rValue)
	return
}

func (c AConfig) unmarshal(value string, t interface{}) (err error) {
	bytes := []byte(value)
	if err = json.Unmarshal(bytes, &t); err != nil {
		err = yaml.Unmarshal(bytes, &t)
	}
	return
}

func getValue(key string, space *storage.Config) (string, error) {
	var value string

	ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)

	f := func() {
		defer cancel()
		value = space.GetValue(key)
	}

	go f()

	<-ctx.Done()
	err := ctx.Err()
	if err.Error() == ContextCancel {
		return value, nil
	}

	return "", err
}

type myConfigChangeListener struct {
	// Implement the methods of ConfigChangeListener interface
}

func (c *myConfigChangeListener) OnChange(event *storage.ChangeEvent) {
	// Handle the changed config values here
	log.Printf("apm apollo listener change event %s\n", event.Namespace)

	for _, eventDetail := range ChangedEvents {
		if eventDetail.NameSpace == event.Namespace {
			threading.GoSafe(func() { eventDetail.EventHandler(event.Changes) })
		}
	}
}

func (c *myConfigChangeListener) OnNewestChange(event *storage.FullChangeEvent) {
	log.Printf("apm apollo listener OnNewestChange event %s\n", event.Namespace)
}
